package org.opentcs.kernel.jjwc;

import jakarta.inject.Inject;
import org.opentcs.components.kernel.Scheduler;
import org.opentcs.components.kernel.services.InternalVehicleService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.*;
import org.opentcs.data.order.Route;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.drivers.vehicle.MovementCommand;
import org.opentcs.drivers.vehicle.VehicleController;
import org.opentcs.strategies.basic.dispatching.OrderReservationPool;
import org.opentcs.strategies.basic.scheduling.ReservationPool;
import org.opentcs.strategies.basic.scheduling.modules.SameDirectionBlockModule;
import org.opentcs.strategies.basic.scheduling.modules.SingleVehicleBlockModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static org.opentcs.components.kernel.Scheduler.POINT_RELAY_BLOCK;

public class DefaultVehicleResService {

  private static final Logger LOG = LoggerFactory.getLogger(DefaultVehicleResService.class);

  private final OrderReservationPool orderReservationPool;

  private final ReservationPool reservationPool;

  private final InternalVehicleService vehicleService;

  private final SingleVehicleBlockModule singleVehicleBlockModule;

  private final SameDirectionBlockModule sameDirectionBlockModule;

//  private final Deque<MovementCommand> parkFutureCommands = new ArrayDeque<>();

  @Inject
  public DefaultVehicleResService(OrderReservationPool orderReservationPool, ReservationPool reservationPool, InternalVehicleService vehicleService, SingleVehicleBlockModule singleVehicleBlockModule, SameDirectionBlockModule sameDirectionBlockModule) {
    this.orderReservationPool = orderReservationPool;
    this.reservationPool = reservationPool;
    this.vehicleService = vehicleService;
    this.singleVehicleBlockModule = singleVehicleBlockModule;
    this.sameDirectionBlockModule = sameDirectionBlockModule;
  }


  /**
   * 判断小车是否处于一个relay block的站点。并且预定的订单的起点库位就在所依赖的block中，并且block中已经存在了小车，
   * <p>
   * 如果出现以上情况，那么需要再往下下发一个站点，让小车离开这种非法站点
   *
   * @return
   */
  public boolean isInRelayBlockPoint(VehicleController client, TCSObjectReference<Vehicle> vehicleRef, TCSObjectReference<TransportOrder> orderRef, int futureOrCurrentPositionIndex) {
    String clientId = client.getId();
    TransportOrder currentOrder = vehicleService.fetchObject(TransportOrder.class, orderRef);
    Point judgePoint = null;
    Vehicle currentVehicle = vehicleService.fetchObject(Vehicle.class, vehicleRef);
    List<Route.Step> steps = currentOrder.getCurrentDriveOrder().getRoute().getSteps();
    if (futureOrCurrentPositionIndex == -1) {
      judgePoint = vehicleService.fetchObject(Point.class, currentVehicle.getCurrentPosition());
    } else {
      judgePoint = steps.get(futureOrCurrentPositionIndex).getDestinationPoint();
    }

    String relayBlockName = judgePoint.getProperty(POINT_RELAY_BLOCK);
    //如果不是依赖block站点也不需要处理
    if (relayBlockName == null) {
      LOG.info("{} 当前站点{}没有依赖", clientId, judgePoint.getName());
      return false;
    }
    //可能存在多个依赖block
    String[] split = relayBlockName.split(",");
    List<Block> blocks = new ArrayList<>();
    for (String blockName : split) {
      Block block = vehicleService.fetchObject(Block.class, blockName);
      if (block == null) {
        LOG.warn("{}在中断泊车任务中，站点{}所以依赖的block:{} 不存在", clientId, judgePoint.getName(), blockName);
        continue;
      }
      blocks.add(block);
    }

    //如果依赖的block不存在，那么也直接返回
    if (blocks.isEmpty()) {
      LOG.info("{}依赖的block：{}不存在", clientId, relayBlockName);
      return false;
    }
    List<TCSObjectReference<TransportOrder>> reservations = orderReservationPool.findReservations(vehicleRef);
    //如果没有预定订单，直接返回false
    if (reservations == null || reservations.isEmpty()) {
      LOG.info("{}没有添加预定订单", clientId);
      return false;
    }

    TransportOrder reservaTransportOrder = vehicleService.fetchObject(TransportOrder.class, reservations.get(0));
    TCSObjectReference<?> destination = reservaTransportOrder.getAllDriveOrders().get(0).getDestination().getDestination();
    String name = destination.getName();
    final TCSObjectReference<?> finalPointRef = getResourceRef(destination);
    if (finalPointRef == null) {
      LOG.info("新订单的终点无法确定，不允许取消泊车");
      return true;
    }
    Set<Block> collect = blocks.stream().filter(blockVar -> blockVar.getMembers().contains(finalPointRef)).collect(Collectors.toSet());
    //如果预约的终点不在依赖的block当中那么就返回false
    if (collect.isEmpty()) {
      LOG.info("{} 预定订单的终点{}不在依赖的block {}当中", clientId, name, relayBlockName);
      return false;
    }

    //如果对应的block为single类型，那么如果block被分配了，就直接不成功，如果是same类型需要判断是否符合方向集合
    //判断对应的依赖block是否已经有小车进入了

    Set<TCSResource<?>> resources = new HashSet<>();
    Point point = vehicleService.fetchObject(Point.class, finalPointRef.getName());
    resources.add(point);
    boolean b = singleVehicleBlockModule.mayAllocate(client, resources);
    if (b) {
      LOG.info("{}依赖的block： {}中没有被占,泊车任务可以终止", clientId, relayBlockName);
    } else {
      LOG.info("{}依赖的block： {}中被占,泊车任务不可以终止，继续下发一步{}再尝试执行", clientId, relayBlockName, steps.get(futureOrCurrentPositionIndex + 1));
    }
    return !b;

  }


  public boolean isCanChange(Scheduler.Client client) {
    return sameDirectionBlockModule.canChangeDirInBLock(client);
  }

//  /**
//   * 在取消泊车任务订单的时候，存储下取消的订单未来指令
//   * @param commands
//   */
//  public void addParkFutureCommands(List<MovementCommand> commands) {
//    parkFutureCommands.addAll(commands);
//  }
//
//  public void clearParkFutureCommands() {
//    parkFutureCommands.clear();
//  }


  private TCSResourceReference<?> getResourceRef(TCSObjectReference<?> destination) {
    if (destination.getReferentClass() == Location.class) {
      Location location = vehicleService.fetchObject(Location.class, destination.getName());
      Set<Location.Link> attachedLinks = location.getAttachedLinks();
      if (attachedLinks.size() != 1) {
        LOG.warn("{}库位link 站点不唯一，无法进行取消泊车任务", destination.getName());
        return null;
      }
      for (Location.Link attachedLink : attachedLinks) {
        return attachedLink.getPoint();
      }

    } else {
      return (TCSResourceReference<?>) destination;
    }
    return null;
  }
  public boolean isPark(TransportOrder transportOrder) {
    if (transportOrder == null) return false;
    return transportOrder.getName().startsWith("Park-") && vehicleService.fetchObject(TransportOrder.class, transportOrder.getReference()).hasState(TransportOrder.State.WITHDRAWN);
  }
}
